Docker 容器如何访问外部网络端以及口映射原理?
不必太纠结于当下,也不必太忧虑未来,当你经历过一些事情的时候,眼前的风景已经和从前不一样了。——村上春树
写在前面
- 整理 Docker 容器如何访问外部网络端以及口映射原理做简单分享
- 理解不足小伙伴帮忙指正
不必太纠结于当下,也不必太忧虑未来,当你经历过一些事情的时候,眼前的风景已经和从前不一样了。——村上春树
我们知道正常情况下,在 Docker 中启动一个容器,这个容器可以自动的访问外部网络,今天我们就来看看 docker 中的容器是如何访问外部网络的?
默认情况下,当我们什么配置都不做,docker 会为每个创建的容器使用 Bridge Network
类型的网络,同时 docker 默认使用过 bridge
的网络驱动
可以通过下面的命令来验证
1 | liruilonger@cloudshell:~$ docker network inspect bridge --format='{{.Driver}}' |
现在我们启动一个 nginx 容器
1 | liruilonger@cloudshell:~$ docker run -d -p 2024:80 --name mynginxs nginx |
映射端口访问正常
同时在容器内部访问 外部网站正常
1 | liruilonger@cloudshell:~$ docker ps |
现在我么来看看容器访问 baidu.com
是如何发生的?在这之前,我需要看一下当前容器的网络配置
1 | liruilonger@cloudshell:~$ docker inspect 704b4427a24d |
之所以能够实现访问外网,下面的配置必不可少
1 | "NetworkSettings": { |
通过上面的配置信息,可以找到有用的信息
IP 地址为: “IPAddress”: “172.17.0.2”,
网关为: “Gateway”: “172.17.0.1”
简单梳理一下流程:
- 首先在容器内发起对
baidu.com
的访问请求 - 请求首先被容器中网络命名空间(
/var/run/docker/netns/29735aa89eef
)对应的网络栈接收 - 容器内的网络栈将检查目标地址是否在容器网络的子网范围内。由于
baidu.com
不在容器网络内,网络栈确定需要将请求发送到容器外部网络 - 所以容器要找网关
172.17.0.1
把请求发出去。这里的网关地址实际上是在安装 docker 是默认创建的桥虚拟接设备docker0
通过下面的命令我们可以看到
1 | liruilonger@cloudshell:~$ ifconfig docker0 |
实际上在创建 容器之后,docker 会默认帮我们做一些事
- 会创建一个容器对应的 Linux 网络命名空间
- 创建一对
veth pair
,将其中一个端口连接到根命名空间中的网桥docker0
上,另一个端口放置在容器命名空间中。 - 在容器命名空间中配置 IP 地址(172.17.0.2),并将该设备激活。
- 在根命名空间中启用 IP 转发功能(通过设置
net.ipv4.ip_forward=1
),同时在容器命名空间配置默认网关(172.17.0.1)。 - 配置 NAT 规则
SNAT
,将容器网络命名空间中的流量转发的源IP地址转化为根命名空间中的IP地址
。
可以通过 sudo iptables -t nat -nL
命令查到POSTROUTING
链中配置的 SNAT
规则
1 | Chain POSTROUTING (policy ACCEPT) |
它将源地址为 172.17.0.0/16(Docker 桥接网络的子网)
的所有数据包的源地址修改为主机的 IP 地址
,并将目标地址设置为 0.0.0.0/0
,表示任何目标地址。这个规则允许位于 Docker 桥接网络中的容器访问外部网络和互联网资源。
- 目标命名空间中的流量将通过默认网关走网桥 IP 地址转发到根命名空间中,并通过根命名空间中的网络设备连接到互联网。
- 所以在到了网关地址对应的 Linux 网桥设备
docker0
之后,因为默认开启了ipv4
转发,即可以简单理解为把宿主机当交换机
,docker0
的流量会直接转发到外部网络
1 | liruilonger@cloudshell:~$ ip route |
- Docker 宿主机的网络栈接收到请求后,宿主机的网络配置设置了
SNAT
,它将转换容器内部的源 IP 地址为宿主机的 IP 地址
,宿主机上的网络栈将根据自己的路由表和网络配置,将请求转发到外部网络,同时以便响应返回时能正确到达容器 - 之后的请求就是宿主机和公网的通行,这里不多描述
所以一般情况下,容器访问外部网络,需要两个因素:
ip_forward
(开启 IPV4 转发)SNAT/MASQUERADE
(配置 SNAT/MASQUERADE)
所以如果发现容器内访问不了外部网络,则需要确认系统的ip_forward
是否已打开。或者检查docker daemon
启动的时候--ip-forward
参数是不是被设置成false
了,如果是的话,则需要设置--ip-forward=true
重新启动 Docker,Docker 会打开主机的 ip forward。
即从容器网段出来访问外部网络的包,都要做一次MASQUERADE
,即出去的包都用主机的IP地址替换源地址
。
下面为当前容器宿主机所有链上的 nat
表的防火墙规则
1 | liruilonger@cloudshell:~$ sudo iptables -t nat -nL |
这里我们顺便看一下,容器端口映射的原理,实际上主要在 DOCKER
这条自定义链上配置了 DNAT
1 | Chain DOCKER (2 references) |
第二个规则是针对源地址为0.0.0.0/0
,目标地址为0.0.0.0/0
,目标端口为2024的TCP数据包。这个规则将数据包的目标地址修改为172.17.0.2:80,即将数据包重定向到172.17.0.2的端口80
。
这里实际上进行了端口映射的操作,也就是 DNAT 发生的地方,它有两处引用
分别是PREROUTING
链和OUTPUT
链,意味着从外面发到本机和本地进程访问本机(由 iptables 匹配规则ADDRTYPE match dst-type LOCAL
指定)的 2024 端口的包目的地址都会被修改成 172.17.0.2:80。
关于 docker 的端口映射, 除了使用docker ps
命令给出容器的端口映射关系,还可以使用docker port
命令查看容器的端口在主机上的映射
这里简单分享一些 DNAT 和 SNAT 的知识
SNAT/DNAT 认知
DNAT
DNAT根据指定条件 修改数据包的目标IP地址和目标端口
。DNAT 的原理和我们上文讨论的端口转发原理差不多,差别是端口转发不修改IP地址。使用iptables做目的地址转换的一个典型例子如下:
1 | iptables -t nat -A PREROUTING -d 1.2.3.4 -p tcp -dport 80 -j DNAT --to-destination 10.20.30.40:8080 |
- -j DNAT 表示目的地址转换
- -d 1.2.3.4 -p tcp –dport 80 表示匹配的包,条件是访问目的地址和端口为1.2.3.4:80的TCP包
- –to-destination 表示将该包的目的地址和端口修改成 10.20.30.40:8080。
同样,DNAT不修改协议。如果要匹配网卡,可以用 -i eth0
指定收到包的网卡(i 是 input 的缩写)。需要注意的是,DNAT 只发生在 nat表的 PREROUTING
链和 OUTPUT
,这也是我们要指定收到包的网卡而不是发出包的网卡的原因
当涉及转发的目的IP地址是外机时,需要确保启用 ip forward 功能,即把 Linux :
1 | echo 1 > /proc/sys/net/ipv4/ip_forward |
SNAT/ 网络地址欺骗
神秘的网络地址欺骗其实是SNAT的一种。SNAT 根据指定条件修改数据包的源IP地址,即 DNAT 的逆操作。与 DNAT 的限制类似,SNAT 策略只能发生在 nat 表的 POSTROUTING 链 和 INPUT
链。
1 | ipttables -t nat -A POSTROUTING -s 192.168.26.12 -o eth0 -j SNAT -to-source 10.127.16.1 |
- -j SNAT表示源地址转换
- -s 192.168.1.12 表示匹配的包源地址是 192.168.1.12,
- –to-source 表示将该包的源地址修改成 10.172.16.1。与DNAT类似
- -o eth0(o是output的缩写)匹配发包的网卡
至于网络地址伪装,与SNAT类似,其实就是一种特殊的源地址转换,报文从哪个网卡出就用该网卡上的IP地址替换该报文的源地址,具体用哪个IP地址由内核决定。下面这条规则的意思是:源地址是 10.8.0.0/16 的报文都做一次 Masq
。
1 | iptable -t nat -A POSTROUTING -s 10.8.0.0/16 -j MASQUERADE |
博文部分内容参考
© 文中涉及参考链接内容版权归原作者所有,如有侵权请告知 :)
© 2018-至今 liruilonger@gmail.com, All rights reserved. 保持署名-非商用-相同方式共享(CC BY-NC-SA 4.0)
Docker 容器如何访问外部网络端以及口映射原理?
https://liruilongs.github.io/2024/03/17/docker/Docker 容器如何访问外部网络端以及口映射原理?/